package org.activequant.core.util;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.activequant.core.types.TimeStamp;

/**
 * Similar to SimpleDateFormat, but:
 * <ul>
 *    <li> uses UTC time zone by the default.
 *    <li> allows nanosecond part to be printed (pattern <em>nnnnnn</em>)
 * </ul>
 * Example:
 * <pre>
 * YYYY/MM/dd HH:mm:ss SSSnnnnnn
 * </pre>
 * may output 
 * <pre>
 * 2007/12/02 19:03:16.670123456
 * </pre>
 * Nanosecond (like other date-related parts) is optional. Therefore, if pattern
 * does not contain nanoseconds, this class works like UTC-based SimpleDateFormat.
 * <br>
 * <b>History:</b><br>
 *  - [02.12.2007] Created (Mike Kroutikov)<br>
 *  - [19.06.2008] Fixing test cases (Ulrich Staudinger)<br>
 *
 *  @author Mike Kroutikov
 */
public class TimeStampFormat {
	/**
	 * private static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");
	 */
	private static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");
	/**
	 * private static final String NANO_PATTERN="nnnnnn";
	 */
	private static final String NANO_PATTERN="nnnnnn";
	/**
	 * private final String format;
	 */
	private final String format;
	/**
	 * private final int index;
	 */
	private final int index;
	/**
	 * private final SimpleDateFormat dateFormat;
	 */
	private final SimpleDateFormat dateFormat;
	/**
	 * Constructs a TimeStampFormat using the given format(String) and a default Locale.<br/>
	 * -Sets its associated format(String) with the given format(String).<br/>
	 * <strong>If it detects a NANO_PATTERN("nnnnnn") in the format(String):</strong><br/>
	 * -sets its associated index(int) with that position/index<br/>
	 * -initializes its associated dateFormat(SimpleDateFormat) to a new SimpleDateFormat using the given format(String)(removing the nano pattern part), default Locale and UTC_ZONE TimeZone<br/>
	 * -counts the number of quotes("'") in the format(String) and decreases the associated index(int) by that number.<br/>
	 * <strong>Otherwise:</strong><br/>
	 * -sets its associated index(int) to -1<br/>
	 * -initializes its associated dateFormat(SimpleDateFormat) to a new SimpleDateFormat using the given format(String), default Locale and UTC_ZONE TimeZone
	 * @param format
	 */
	public TimeStampFormat(String format) {
		this(format, Locale.getDefault());
	}
	/**
	 * Constructs a TimeStampFormat using the given format(String) and locale(Locale).<br/>
	 * -Sets its associated format(String) with the given format(String).<br/>
	 * <strong>If it detects a NANO_PATTERN("nnnnnn") in the format(String):</strong><br/>
	 * -sets its associated index(int) with that position/index<br/>
	 * -initializes its associated dateFormat(SimpleDateFormat) to a new SimpleDateFormat using the given format(String)(removing the nano pattern part), locale(Locale) and UTC_ZONE TimeZone<br/>
	 * -counts the number of quotes("'") in the format(String) and decreases the associated index(int) by that number.<br/>
	 * <strong>Otherwise:</strong><br/>
	 * -sets its associated index(int) to -1<br/>
	 * -initializes its associated dateFormat(SimpleDateFormat) to a new SimpleDateFormat using the given format(String), locale(Locale) and UTC_ZONE TimeZone
	 * @param format
	 * @param locale
	 */
	public TimeStampFormat(String format, Locale locale) {
		this.format = format;
		
		int idx = format.indexOf(NANO_PATTERN);
		if(idx < 0) {
			// no nanos needed - works like SimpleDateFormat
			dateFormat = new SimpleDateFormat(format, locale);
			index = -1;
		} else {
			// remove nanos pattern, and pass the rest to the date formatter
			dateFormat = new SimpleDateFormat(format.replace(NANO_PATTERN, ""), locale);
			
			// adjust index for the quotes 
			int quotes = 0;
			for(int i = 0; i < idx; i++) {
				if(format.charAt(i) == '\'') quotes++;
			}
			if((quotes % 2) != 0) {
				// NANO_PATTERN within a string literal - not yet supported!
				throw new IllegalArgumentException("pattern 'nnnnnn' can not be used withing quoted format string - check single quotes: " + format);
			}
			idx -= quotes;
			index = idx;
		}
		
		dateFormat.setTimeZone(UTC_ZONE);
	}
	/**
	 * sets the TimeZone of the associated dateFormat(SimpleDateFormat)
	 * @param tz
	 */
	public void setTimeZone(TimeZone tz) {
		dateFormat.setTimeZone(tz);
	}
	/**
	 * returns a formatted timeStamp(String) for the given timeStamp(TimeStamp). The format can include a NANO_PATTERN("nnnnnn").
	 * @param timeStamp
	 * @return
	 */
	public String format(TimeStamp timeStamp) {
		if(index < 0) {
			return dateFormat.format(timeStamp.getDate());
		} else {
			StringBuilder bld = new StringBuilder(dateFormat.format(timeStamp.getDate()));
			bld.insert(index, formatNanos(timeStamp));
			return bld.toString();
		}
	}
	/**
	 * Returns a String representing the formatted nanosecond part of the given ts(TimeStamp).<br/> 
	 * (It is always 6 digits, prefixed with 0's as necessary)
	 * @param ts
	 * @return
	 */
	private static String formatNanos(TimeStamp ts) {
		int nanos = (int) (ts.getNanoseconds() % 1000000L);
		String out = Integer.toString(nanos);
		switch(out.length()) {
		case 1: return "00000" + out;
		case 2: return "0000" + out;
		case 3: return "000" + out;
		case 4: return "00" + out;
		case 5: return "0" + out;
		default: return out;
		}
	}
	/**
	 * returns a TimeStamp parsed from the given value(String). The format can include a NANO_PATTERN("nnnnnn").
	 * @param value
	 * @return
	 */
	public TimeStamp parse(String value) {
		try {
			if(index < 0) {
				ParsePosition pos = new ParsePosition(0);
				Date date = dateFormat.parse(value, pos);
				if(pos.getIndex() < value.length()) {
					throw new IllegalArgumentException("not all input has been consumed");
				}
				return new TimeStamp(date);
			} else {
				String nanoString = value.substring(index, index + NANO_PATTERN.length());
				int nanos = Integer.parseInt(nanoString);
				ParsePosition pos = new ParsePosition(0);
				Date date = dateFormat.parse(
						value.substring(0, index) 
						+ value.substring(index + NANO_PATTERN.length()), pos);
				if(pos.getIndex() < value.length() - NANO_PATTERN.length()) {
					throw new IllegalArgumentException("not all input has been consumed"); 
				}
				return new TimeStamp(date, nanos);
			}
		} catch(Exception ex) {
			throw new IllegalArgumentException("bad format '" + value + "', expected pattern: '" + format + "'", ex);
		}
	}
	/**
	 * returns the associated format(String)
	 */
	public String toString() {
		return format;
	}
}
